feat: Range 组件支持 disabled 数组形式#1068
feat: Range 组件支持 disabled 数组形式#1068EmilyyyLiu wants to merge 4 commits intoreact-component:masterfrom
Conversation
- Add support for disabled={[boolean, boolean, ...]} to disable specific handles
- Disabled handles cannot be dragged or moved via keyboard
- Non-disabled handles cannot cross disabled handles
- Clicking slider moves nearest non-disabled handle
- In editable mode, disabled handles cannot be deleted
- Track draggable is disabled when any handle is disabled
- Backward compatible with boolean disabled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Someone is attempting to deploy a commit to the React Component Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough此变更为滑块组件引入按手柄禁用能力:扩展 Changes
Sequence DiagramsequenceDiagram
actor User
participant Slider
participant Context as SliderContext
participant Handle
participant Hooks as useDrag/useOffset
participant Tracks
User->>Slider: 设置/更新 disabled(boolean|boolean[])
Slider->>Slider: 归一化并计算 isHandleDisabled(index)
Slider->>Context: 注入 isHandleDisabled
Slider->>Tracks: 传递 isHandleDisabled
User->>Handle: 交互(点击/拖动/键盘)
Handle->>Context: 读取 isHandleDisabled(index)
alt 手柄已禁用
Handle->>User: 标记为 aria-disabled/tabIndex/class 并拒绝交互(早退)
else 手柄可用
Handle->>Hooks: 发起拖动/偏移请求(包含 index)
Hooks->>Context: 查询 isHandleDisabled 以计算边界/允许性
Hooks->>Slider: 提交值变更
Slider->>User: 更新 UI/值
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to disable specific handles in a range slider by passing an array of booleans to the disabled prop. The changes include styling for disabled handles, event suppression for keyboard and mouse interactions, and logic to prevent enabled handles from crossing disabled ones. The review feedback highlights a few edge cases: the click-to-move logic needs to respect disabled handle boundaries, the pushable distance should be accounted for when calculating boundaries against disabled handles, and the global disabled state derivation should be refined to avoid incorrectly applying disabled styles to the entire component when only specific handles are restricted.
| if (isHandleDisabled) { | ||
| for (let i = valueIndex - 1; i >= 0; i -= 1) { | ||
| if (isHandleDisabled(i)) { | ||
| minBound = nextValues[i]; | ||
| break; | ||
| } | ||
| } | ||
| for (let i = valueIndex + 1; i < nextValues.length; i += 1) { | ||
| if (isHandleDisabled(i)) { | ||
| maxBound = nextValues[i]; | ||
| break; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The boundary calculation for the active handle should account for the pushable distance when neighboring handles are disabled. If a neighbor is disabled, the current handle must stop at a distance that respects the pushable requirement, otherwise the subsequent push logic will attempt to move the disabled handle.
| if (isHandleDisabled) { | |
| for (let i = valueIndex - 1; i >= 0; i -= 1) { | |
| if (isHandleDisabled(i)) { | |
| minBound = nextValues[i]; | |
| break; | |
| } | |
| } | |
| for (let i = valueIndex + 1; i < nextValues.length; i += 1) { | |
| if (isHandleDisabled(i)) { | |
| maxBound = nextValues[i]; | |
| break; | |
| } | |
| } | |
| } | |
| if (isHandleDisabled) { | |
| const pushNum = typeof pushable === 'number' ? pushable : 0; | |
| for (let i = valueIndex - 1; i >= 0; i -= 1) { | |
| if (isHandleDisabled(i)) { | |
| minBound = nextValues[i] + (valueIndex - i) * pushNum; | |
| break; | |
| } | |
| } | |
| for (let i = valueIndex + 1; i < nextValues.length; i += 1) { | |
| if (isHandleDisabled(i)) { | |
| maxBound = nextValues[i] - (i - valueIndex) * pushNum; | |
| break; | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
如果被禁用手柄,那不应该能被被移动吧
There was a problem hiding this comment.
它说的是 pushable,指的是拉其他的把被禁用的被动推走。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1068 +/- ##
==========================================
- Coverage 99.39% 99.15% -0.24%
==========================================
Files 14 14
Lines 661 713 +52
Branches 191 218 +27
==========================================
+ Hits 657 707 +50
- Misses 4 6 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7b60ace to
a7db356
Compare
341c634 to
4f28cc4
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Slider.tsx (1)
341-355:⚠️ Potential issue | 🟠 Major
editable模式下disabled[]会在增删后错位。尤其是 uncontrolled 用法下,这里只对
rawValues做了splice,disabled仍按原始 index 解释。比如[false, true, false]删除第 0 个 handle 后,原来禁用的中间 handle 会变成 index 0 并重新可操作;插入新 handle 也会把后续禁用位整体右移。现在只拦住了“当前 index 上的 disabled handle”,但没保证“同一个 handle 持续 disabled”。建议为 editable 模式维护和 values 同步的 handle 元数据,或者明确限制这组组合必须由外部受控并同步更新。Also applies to: 399-408
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Slider.tsx` around lines 341 - 355, The onDelete handler (and corresponding insert logic around the insert function at lines ~399-408) mutates rawValues but leaves the parallel disabled[] array indexed to original handles, causing disabled flags to shift; update the implementation so editable mode keeps per-handle metadata in sync with values—either splice the disabled array the same way you splice rawValues (e.g., cloneDisabled.splice(index,1) on delete and cloneDisabled.splice(index,0,newFlag) on insert) and pass the updated disabled array/state through the same change flow, or introduce a single array of handle metadata (e.g., handleMeta[]) that is kept in lockstep with rawValues and is updated in onDelete, onInsert, and wherever values change; ensure isHandleDisabled and any callers read from this synchronized source rather than the original static disabled[] prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Slider.tsx`:
- Around line 191-206: The aggregated disabled computation is inconsistent with
per-handle checks: update the computed "disabled" (currently using
rawDisabled.every) to consider the current number of handles (use
rawValues.length) so it only returns true when rawDisabled is an array of length
equal to rawValues.length and every entry is truthy; keep isHandleDisabled
(function name) as-is but ensure it still falls back to false for missing
indices. In short, change the logic for disabled to: if rawDisabled is boolean
return it; if it's an array, return rawDisabled.length === rawValues.length &&
rawDisabled.every(Boolean); leave isHandleDisabled(index) unchanged.
In `@tests/Range.test.tsx`:
- Around line 985-1005: The test's claim that it covers the nearestIndex === -1
path is incorrect because with disabled={[true,true,true]} the
changeToCloseValue early-returns at if (!disabled), so the nearestIndex branch
is never reached; either remove or update the misleading comment/description in
the it(...) block, or replace the test with one that actually executes the
nearestIndex === -1 branch by ensuring changeToCloseValue runs (e.g., make at
least one handle enabled and construct a click position that yields nearestIndex
=== -1 so the code path in Slider.tsx referencing nearestIndex is exercised);
refer to the test case name in Range.test.tsx and the changeToCloseValue /
nearestIndex symbols to locate where to change the test and its assertion.
- Around line 961-983: The test for "draggableTrack disabled when any handle is
disabled" is likely a false positive because it builds a custom
mouseDown/mouseMove sequence that doesn't match the track drag startup used
elsewhere; reuse the existing doMouseMove helper so the test exercises the same
drag path and ensure the mouseDown event includes pageX/pageY getters and a
preventDefault implementation (and set pageX/pageY on the mouseMove) so the
draggableTrack logic is triggered; update the spec to call doMouseMove (or
mirror its behavior) when interacting with the rc-slider-track and assert
onChange remains uncalled when one handle is disabled.
---
Outside diff comments:
In `@src/Slider.tsx`:
- Around line 341-355: The onDelete handler (and corresponding insert logic
around the insert function at lines ~399-408) mutates rawValues but leaves the
parallel disabled[] array indexed to original handles, causing disabled flags to
shift; update the implementation so editable mode keeps per-handle metadata in
sync with values—either splice the disabled array the same way you splice
rawValues (e.g., cloneDisabled.splice(index,1) on delete and
cloneDisabled.splice(index,0,newFlag) on insert) and pass the updated disabled
array/state through the same change flow, or introduce a single array of handle
metadata (e.g., handleMeta[]) that is kept in lockstep with rawValues and is
updated in onDelete, onInsert, and wherever values change; ensure
isHandleDisabled and any callers read from this synchronized source rather than
the original static disabled[] prop.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 462e9270-165c-464f-a6ac-41f3c062772f
📒 Files selected for processing (10)
assets/index.lessdocs/demo/disabled-handle.mddocs/examples/disabled-handle.tsxsrc/Handles/Handle.tsxsrc/Slider.tsxsrc/Tracks/index.tsxsrc/context.tssrc/hooks/useDrag.tssrc/hooks/useOffset.tstests/Range.test.tsx
| it('draggableTrack disabled when any handle is disabled', () => { | ||
| const onChange = jest.fn(); | ||
| const { container } = render( | ||
| <Slider range={{ draggableTrack: true }} defaultValue={[0, 50]} disabled={[false, true]} onChange={onChange} />, | ||
| ); | ||
|
|
||
| // Try to drag track - should not work because one handle is disabled | ||
| const track = container.getElementsByClassName('rc-slider-track')[0]; | ||
| const mouseDown = createEvent.mouseDown(track); | ||
| Object.defineProperties(mouseDown, { | ||
| clientX: { get: () => 0 }, | ||
| clientY: { get: () => 0 }, | ||
| }); | ||
| fireEvent(track, mouseDown); | ||
|
|
||
| // Drag | ||
| const mouseMove = createEvent.mouseMove(document); | ||
| (mouseMove as any).pageX = 20; | ||
| (mouseMove as any).pageY = 20; | ||
| fireEvent(document, mouseMove); | ||
|
|
||
| expect(onChange).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
这个拖拽用例现在可能是“假通过”。
这里没有复用 doMouseMove,mouseDown 也没补 pageX/pageY 和 preventDefault,很可能根本没走到现有 track drag 用例相同的启动路径。这样即使 track 仍然可拖,onChange 也可能不会触发,导致用例误报通过。建议直接复用同一个 helper。
可参考的最小改法
- const track = container.getElementsByClassName('rc-slider-track')[0];
- const mouseDown = createEvent.mouseDown(track);
- Object.defineProperties(mouseDown, {
- clientX: { get: () => 0 },
- clientY: { get: () => 0 },
- });
- fireEvent(track, mouseDown);
-
- // Drag
- const mouseMove = createEvent.mouseMove(document);
- (mouseMove as any).pageX = 20;
- (mouseMove as any).pageY = 20;
- fireEvent(document, mouseMove);
+ doMouseMove(container, 0, 20, 'rc-slider-track');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/Range.test.tsx` around lines 961 - 983, The test for "draggableTrack
disabled when any handle is disabled" is likely a false positive because it
builds a custom mouseDown/mouseMove sequence that doesn't match the track drag
startup used elsewhere; reuse the existing doMouseMove helper so the test
exercises the same drag path and ensure the mouseDown event includes pageX/pageY
getters and a preventDefault implementation (and set pageX/pageY on the
mouseMove) so the draggableTrack logic is triggered; update the spec to call
doMouseMove (or mirror its behavior) when interacting with the rc-slider-track
and assert onChange remains uncalled when one handle is disabled.
| it('all handles disabled: find nearest enabled returns -1', () => { | ||
| // This test specifically covers line 426 in Slider.tsx | ||
| // When all handles are disabled and clicking near one, | ||
| // the nearestIndex search returns -1 and returns early | ||
| const onChange = jest.fn(); | ||
| const { container } = render( | ||
| <Slider range defaultValue={[0, 50, 100]} disabled={[true, true, true]} onChange={onChange} />, | ||
| ); | ||
|
|
||
| // Click at position 10 (near first disabled handle) | ||
| const rail = container.querySelector('.rc-slider-rail'); | ||
| const mouseDown = createEvent.mouseDown(rail); | ||
| Object.defineProperties(mouseDown, { | ||
| clientX: { get: () => 10 }, | ||
| clientY: { get: () => 10 }, | ||
| }); | ||
| fireEvent(rail, mouseDown); | ||
|
|
||
| // Should not trigger onChange because all handles are disabled | ||
| expect(onChange).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
这条“覆盖 nearestIndex === -1”的说明不成立。
disabled={[true, true, true]} 时,src/Slider.tsx 在 changeToCloseValue 入口的 if (!disabled) 处就已经返回,根本到不了后面的 nearestIndex === -1。所以这条用例实际上只是再次验证“全部 disabled 时点击无效”,并没有覆盖注释里说的路径。建议删掉这段误导性的覆盖说明,或者改成一个真正可达的场景。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/Range.test.tsx` around lines 985 - 1005, The test's claim that it
covers the nearestIndex === -1 path is incorrect because with
disabled={[true,true,true]} the changeToCloseValue early-returns at if
(!disabled), so the nearestIndex branch is never reached; either remove or
update the misleading comment/description in the it(...) block, or replace the
test with one that actually executes the nearestIndex === -1 branch by ensuring
changeToCloseValue runs (e.g., make at least one handle enabled and construct a
click position that yields nearestIndex === -1 so the code path in Slider.tsx
referencing nearestIndex is exercised); refer to the test case name in
Range.test.tsx and the changeToCloseValue / nearestIndex symbols to locate where
to change the test and its assertion.
src/Slider.tsx
Outdated
| if (typeof rawDisabled === 'boolean') { | ||
| return rawDisabled; | ||
| } | ||
| return rawDisabled.every((d) => d); |
There was a problem hiding this comment.
这里如果给的是 disabled={[true]} ,但是有 2 个节点也会被当成全部 disabled。
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/examples/disabled-handle.tsx`:
- Around line 55-58: The description text is inconsistent with the initial state
of BasicDisabledHandle (disabled=[true, false, false, true]) which disables
track dragging; update the paragraph so it accurately reflects behavior (e.g.,
"When any handle is disabled, track dragging is disabled; toggle checkboxes to
enable/disable handles and, when all handles are enabled, drag the track to move
the range.") or alternatively change BasicDisabledHandle's initial disabled
array to match the original description; refer to the JSX around
BasicDisabledHandle and the paragraph node that currently reads "Drag the track
area to move the range" to apply the fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f81268eb-5a58-4350-9bea-879d4ee5856d
📒 Files selected for processing (2)
docs/examples/disabled-handle.tsxsrc/Slider.tsx
ded7506 to
b456606
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/Slider.tsx (2)
508-517:⚠️ Potential issue | 🟡 Minor缺少
rawValues依赖项。管道警告:
React Hook React.useEffect has a missing dependency: 'rawValues'。虽然这可能是已有代码,但缺少此依赖可能导致闭包陈旧问题。🔧 建议的修复
React.useEffect(() => { if (keyboardValue !== null) { const valueIndex = rawValues.indexOf(keyboardValue); if (valueIndex >= 0) { handlesRef.current.focus(valueIndex); } } setKeyboardValue(null); - }, [keyboardValue]); + }, [keyboardValue, rawValues]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Slider.tsx` around lines 508 - 517, The effect watching keyboardValue is missing rawValues in its dependency array which can cause stale closures; update the React.useEffect (the effect that references keyboardValue, rawValues, handlesRef, and calls setKeyboardValue) to include rawValues in the dependency array (i.e., [keyboardValue, rawValues]) so the valueIndex lookup uses the current rawValues; if rawValues is not stable, ensure it's memoized (e.g., via useMemo) or derive a stable key instead to avoid excessive re-renders.
536-543:⚠️ Potential issue | 🟡 Minor缺少
draggingValue和rawValues依赖项。管道警告:
React Hook React.useEffect has missing dependencies: 'draggingValue' and 'rawValues'。当dragging变为false时,使用的draggingValue和rawValues可能是陈旧的值。🔧 建议的修复
React.useEffect(() => { if (!dragging) { const valueIndex = rawValues.lastIndexOf(draggingValue); handlesRef.current.focus(valueIndex); } - }, [dragging]); + }, [dragging, draggingValue, rawValues]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Slider.tsx` around lines 536 - 543, The effect that auto-focuses the updated handle is missing dependencies and can read stale values; update the useEffect dependency array to include draggingValue and rawValues (in addition to dragging) and inside the effect recompute valueIndex = rawValues.lastIndexOf(draggingValue) when dragging is false, guarding that handlesRef.current exists and valueIndex is valid before calling handlesRef.current.focus(valueIndex); keep the existing dragging boolean (draggingIndex !== -1) and use the same symbols (dragging, draggingValue, rawValues, handlesRef) so the hook reruns with fresh values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Slider.tsx`:
- Around line 190-210: The disabled calculation references rawValues before it
is declared causing the ESLint "'rawValues' was used before it was defined"
error; move the useMemo that defines disabled and the useCallback
isHandleDisabled to after rawValues is declared (or refactor them to compute
lazily so they read rawValues at call time), update their dependency arrays to
include rawValues and rawDisabled, and then relocate or re-evaluate any callers
(notably useOffset which depends on isHandleDisabled) so useOffset is invoked
after these definitions—alternatively split isHandleDisabled into a part that
doesn't depend on rawValues (e.g., boolean rawDisabled check) and a part that
uses rawValues to avoid reordering large blocks.
---
Outside diff comments:
In `@src/Slider.tsx`:
- Around line 508-517: The effect watching keyboardValue is missing rawValues in
its dependency array which can cause stale closures; update the React.useEffect
(the effect that references keyboardValue, rawValues, handlesRef, and calls
setKeyboardValue) to include rawValues in the dependency array (i.e.,
[keyboardValue, rawValues]) so the valueIndex lookup uses the current rawValues;
if rawValues is not stable, ensure it's memoized (e.g., via useMemo) or derive a
stable key instead to avoid excessive re-renders.
- Around line 536-543: The effect that auto-focuses the updated handle is
missing dependencies and can read stale values; update the useEffect dependency
array to include draggingValue and rawValues (in addition to dragging) and
inside the effect recompute valueIndex = rawValues.lastIndexOf(draggingValue)
when dragging is false, guarding that handlesRef.current exists and valueIndex
is valid before calling handlesRef.current.focus(valueIndex); keep the existing
dragging boolean (draggingIndex !== -1) and use the same symbols (dragging,
draggingValue, rawValues, handlesRef) so the hook reruns with fresh values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a285a5ff-c125-4412-bbd9-bd2cec8996ba
📒 Files selected for processing (2)
docs/examples/disabled-handle.tsxsrc/Slider.tsx
✅ Files skipped from review due to trivial changes (1)
- docs/examples/disabled-handle.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/examples/disabled-handle.tsx`:
- Around line 112-116: The paragraph text is inaccurate: because the component
DraggableTrackWithDisabled is initialized with
disabled={[true,false,false,true], and the Tracks implementation disables track
dragging whenever any handle is disabled, track dragging is actually disabled
here; update the paragraph copy to state that track dragging is disabled when
any handle is disabled (or alternatively change the disabled prop on
DraggableTrackWithDisabled to only disable the middle handles if you want
dragging enabled) and mention the disabled prop so the example and description
match actual behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1b54edc7-ee41-4327-9c1d-e2abc6449188
📒 Files selected for processing (2)
docs/examples/disabled-handle.tsxsrc/Slider.tsx
docs/examples/disabled-handle.tsx
Outdated
| <p style={{ marginTop: 8, color: '#999', fontSize: 12 }}> | ||
| First and last handles are disabled (boundaries). | ||
| Drag track to move middle handles together. | ||
| Click track to add/delete handles. | ||
| </p> |
There was a problem hiding this comment.
文档描述与实际行为不一致
根据 src/Tracks/index.tsx:20-28 的实现,当任意手柄被禁用时,轨道拖拽功能会被禁用。但此处 DraggableTrackWithDisabled 初始状态 disabled={[true, false, false, true]},第一和最后一个手柄已被禁用,因此轨道拖拽实际上是不可用的。
建议修正描述以反映实际行为:
📝 建议修改
<p style={{ marginTop: 8, color: '#999', fontSize: 12 }}>
First and last handles are disabled (boundaries).
- Drag track to move middle handles together.
- Click track to add/delete handles.
+ Track dragging is disabled when any handle is disabled.
+ Middle handles can still be dragged individually.
</p>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <p style={{ marginTop: 8, color: '#999', fontSize: 12 }}> | |
| First and last handles are disabled (boundaries). | |
| Drag track to move middle handles together. | |
| Click track to add/delete handles. | |
| </p> | |
| <p style={{ marginTop: 8, color: '#999', fontSize: 12 }}> | |
| First and last handles are disabled (boundaries). | |
| Track dragging is disabled when any handle is disabled. | |
| Middle handles can still be dragged individually. | |
| </p> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/examples/disabled-handle.tsx` around lines 112 - 116, The paragraph text
is inaccurate: because the component DraggableTrackWithDisabled is initialized
with disabled={[true,false,false,true], and the Tracks implementation disables
track dragging whenever any handle is disabled, track dragging is actually
disabled here; update the paragraph copy to state that track dragging is
disabled when any handle is disabled (or alternatively change the disabled prop
on DraggableTrackWithDisabled to only disable the middle handles if you want
dragging enabled) and mention the disabled prop so the example and description
match actual behavior.
Use value prop to determine expected length instead of rawValues. This ensures disabled=[true,true,true] correctly applies global disabled styling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
942b218 to
218f061
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Slider.tsx`:
- Around line 411-433: The current fallback that finds the nearest enabled
handle when isHandleDisabled(valueIndex) is true (using rawValues, newValue and
nearestIndex) can pick a handle across a disabled boundary and allow handles to
jump past disabled handles; change the search in that block so candidates are
limited to handles that can be reached without crossing any disabled handle
between the original index and the candidate: when scanning rawValues for a
nearestIndex, skip any candidate if any index between the original valueIndex
and that candidate is disabled (i.e., ensure the move path contains no disabled
handles), or alternatively restrict the search to the contiguous enabled segment
around the clicked position; update the logic that sets
cloneNextValues[valueIndex], valueIndex and focusIndex accordingly so you never
select a nearestIndex that requires crossing a disabled handle (refer to
isHandleDisabled, rawValues, valueIndex, newValue, cloneNextValues, focusIndex).
- Around line 191-197: The current disabled aggregation in the useMemo (symbols:
rawDisabled, value, disabled) only compares rawDisabled.length to value.length
and thus misses uncontrolled/default handles; change the logic to mirror how
effective handle count is computed elsewhere by using rawValues (or the same
helper that yields rawValues) instead of value when determining expected length
and in the dependency array so disabled is true when rawDisabled aligns with the
actual rendered handles (e.g., use rawValues.length and depend on rawValues,
rawDisabled).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc96bcd9-c708-4d68-816b-2ec8f5bdd931
📒 Files selected for processing (2)
docs/examples/disabled-handle.tsxsrc/Slider.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/examples/disabled-handle.tsx
| const disabled = React.useMemo(() => { | ||
| if (typeof rawDisabled === 'boolean') { | ||
| return rawDisabled; | ||
| }; | ||
|
|
||
| return Array.isArray(value) && value.length === rawDisabled.length && rawDisabled.every(Boolean); | ||
| }, [rawDisabled, value]); |
There was a problem hiding this comment.
聚合 disabled 仍然漏掉非受控 / 默认句柄数。
Line 196 现在只拿 value.length 和 rawDisabled.length 对齐。这样 <Slider range disabled={[true, true]} />、<Slider range count={2} disabled={[true, true, true]} /> 或 <Slider range defaultValue={[10, 20]} disabled={[true, true]} /> 这类初始化里,所有已渲染 handle 都是禁用的,但根节点 disabled 仍会是 false,-disabled 样式和依赖聚合禁用态的分支都会失真。这里需要按和 rawValues 相同的规则推导期望长度,而不是只看 value。
🔧 建议修复
const disabled = React.useMemo(() => {
if (typeof rawDisabled === 'boolean') {
return rawDisabled;
- };
+ }
- return Array.isArray(value) && value.length === rawDisabled.length && rawDisabled.every(Boolean);
- }, [rawDisabled, value]);
+ const sourceValues = Array.isArray(value)
+ ? value
+ : Array.isArray(defaultValue)
+ ? defaultValue
+ : undefined;
+
+ const expectedLength =
+ sourceValues?.length ?? (range ? (count >= 0 ? count + 1 : 2) : 1);
+
+ return (
+ expectedLength > 0 &&
+ rawDisabled.length === expectedLength &&
+ Array.from({ length: expectedLength }, (_, index) => rawDisabled[index] === true).every(Boolean)
+ );
+ }, [rawDisabled, value, defaultValue, range, count]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Slider.tsx` around lines 191 - 197, The current disabled aggregation in
the useMemo (symbols: rawDisabled, value, disabled) only compares
rawDisabled.length to value.length and thus misses uncontrolled/default handles;
change the logic to mirror how effective handle count is computed elsewhere by
using rawValues (or the same helper that yields rawValues) instead of value when
determining expected length and in the dependency array so disabled is true when
rawDisabled aligns with the actual rendered handles (e.g., use rawValues.length
and depend on rawValues, rawDisabled).
| if (isHandleDisabled(valueIndex)) { | ||
| let nearestIndex = -1; | ||
| let nearestDist = mergedMax - mergedMin; | ||
|
|
||
| rawValues.forEach((val, index) => { | ||
| if (!isHandleDisabled(index)) { | ||
| const dist = Math.abs(newValue - val); | ||
| if (dist < nearestDist) { | ||
| nearestDist = dist; | ||
| nearestIndex = index; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // If all handles are disabled, do nothing | ||
| if (nearestIndex === -1) { | ||
| return; | ||
| } | ||
|
|
||
| valueIndex = nearestIndex; | ||
| } | ||
| cloneNextValues[valueIndex] = newValue; | ||
| focusIndex = valueIndex; |
There was a problem hiding this comment.
轨道 / Mark 点击的回退选择会让 handle 穿过禁用边界。
Line 415-423 在最近的 handle 是禁用时,会从整个 rawValues 里找“最近的启用 handle”。这会选到禁用边界另一侧的候选:例如 values=[0, 20, 100]、disabled=[false, true, false] 时点击 30,这里会把 0 改到 30,直接越过 20。更糟的是后面还会排序,禁用标记会跟着索引错位到新的值上。回退搜索需要限制在 newValue 所在的禁用分段内,或者显式过滤掉移动路径上存在禁用 handle 的候选。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Slider.tsx` around lines 411 - 433, The current fallback that finds the
nearest enabled handle when isHandleDisabled(valueIndex) is true (using
rawValues, newValue and nearestIndex) can pick a handle across a disabled
boundary and allow handles to jump past disabled handles; change the search in
that block so candidates are limited to handles that can be reached without
crossing any disabled handle between the original index and the candidate: when
scanning rawValues for a nearestIndex, skip any candidate if any index between
the original valueIndex and that candidate is disabled (i.e., ensure the move
path contains no disabled handles), or alternatively restrict the search to the
contiguous enabled segment around the clicked position; update the logic that
sets cloneNextValues[valueIndex], valueIndex and focusIndex accordingly so you
never select a nearestIndex that requires crossing a disabled handle (refer to
isHandleDisabled, rawValues, valueIndex, newValue, cloneNextValues, focusIndex).
功能概述
Range 组件支持
disabled属性传入数组,用于单独禁用特定滑块手柄。关联需求:ant-design/ant-design#10143
API 变更
功能特性
disabled={[boolean, boolean, ...]}数组形式disabled的原有用法测试覆盖
变更文件
src/Slider.tsx- 核心逻辑src/Handles/Handle.tsx- 手柄禁用状态src/Tracks/index.tsx- 轨道拖拽限制src/hooks/useDrag.ts- 拖拽逻辑src/hooks/useOffset.ts- 位置计算边界src/context.ts- 上下文类型tests/Range.test.tsx- 测试用例docs/examples/disabled-handle.tsx- 示例docs/demo/disabled-handle.md- 文档assets/index.less- 禁用样式检查清单
Summary by CodeRabbit
发布说明
新功能
文档
样式
测试